// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"flag"
	"fmt"
	"net"
	"os"
	"strconv"
	"strings"
)

var serializer *Serializer

func die(err error) {
	fmt.Fprintf(os.Stderr, "FAILED: %s\n\n", err)
	os.Exit(1)
}

func usage() {
	fmt.Fprintf(os.Stderr, "usage: %s <laddr>:<lport> <toaddr>:<toport>\n", os.Args[0])
	flag.PrintDefaults()
	os.Exit(2)
}

func doCopy(p Prefix, name string, from, to net.Conn, reply chan string) {
	defer func() {
		from.Close()
		to.Close()
		reply <- name
	}()
	var b [4 * 1024]byte
	for {
		n, e := from.Read(b[:])
		if e != nil || n == 0 {
			return
		}
		serializer.dataCh <- DataMsg{p, b[:n]}
		for m := 0; m < n; {
			qty, e := to.Write(b[m:n])
			if e != nil {
				return
			}
			m += qty
		}
	}
}

func generateConnId(c net.Conn) int {
	s := c.RemoteAddr().String()
	id, _ := strconv.Atoi(s[strings.LastIndex(s, ":")+1:])
	return id
}

func sendComment(id int, s string) {
	serializer.commentCh <- CommentMsg{Prefix{id, '*'}, s}
}

func serve(from net.Conn, raddr string) {
	id := generateConnId(from)
	sendComment(id, fmt.Sprintf("client connected (%s)", from.RemoteAddr()))
	to, e := net.Dial("tcp", raddr)
	if e != nil {
		from.Close()
		fmt.Fprintf(os.Stderr, "%v\n", e)
		return
	}
	s := fmt.Sprintf("(%s->%s)", from.RemoteAddr(), to.LocalAddr())
	sendComment(id, "server connected "+s)
	c := make(chan string, 2)
	go doCopy(Prefix{id, '|'}, "client", from, to, c)
	go doCopy(Prefix{id, '-'}, "server", to, from, c)
	sendComment(id, "disconnected by "+<-c+" "+s)
}

func runListener(laddr, raddr string) {
	l, e := net.Listen("tcp", laddr)
	if e != nil {
		die(e)
	}
	for {
		s, e := l.Accept()
		if e != nil {
			die(e)
		}
		go serve(s, raddr)
	}
	l.Close()
}

func main() {
	flag.Parse()
	if flag.NArg() != 2 {
		usage()
	}

	o := NewOutput()
	go o.Run()

	serializer = NewSerializer()
	go serializer.Run(o.PrefixChangedCh, o.DataCh, o.CommentCh)

	runListener(flag.Arg(0), flag.Arg(1))
}
